{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "1961db5f",
   "metadata": {},
   "source": [
    "# CRAG: Corrective RAG\n",
    "\n",
    "이번 튜토리얼은 **Corrective RAG (CRAG)** 전략을 사용하여 RAG 기반 시스템을 개선하는 방법을 다룹니다. \n",
    "\n",
    "CRAG는 검색된 문서들에 대한 자기 반성(self-reflection) 및 자기 평가(self-evaluation) 단계를 포함하여, 검색-생성 파이프라인을 정교하게 다루는 접근법입니다.\n",
    "\n",
    "![crag](./assets/langgraph-crag.png)\n",
    "\n",
    "---\n",
    "\n",
    "**CRAG 란?**\n",
    "\n",
    "**Corrective-RAG (CRAG)** 는 RAG(Retrieval Augmented Generation) 전략에서 **검색 과정에서 찾아온 문서를 평가하고, 지식을 정제(refine) 하는 단계를 추가한 방법론**입니다. 이는 생성에 앞서 검색 결과를 점검하고 필요하다면 보조적인 검색을 수행하며, 최종적으로 품질 높은 답변을 생성하기 위한 일련의 프로세스를 포함합니다.\n",
    "\n",
    "CRAG의 핵심 아이디어는 다음과 같습니다. \n",
    "\n",
    "[논문(Corrective Retrieval Augmented Generation) 링크](https://arxiv.org/pdf/2401.15884.pdf)\n",
    "\n",
    "1. 검색된 문서 중 하나 이상이 사전 정의된 관련성 임계값(retrieval validation score) 을 초과하면 생성 단계로 진행합니다.\n",
    "2. 생성 전에 지식 정제 단계를 수행합니다.\n",
    "3. 문서를 \"knowledge strips\" 로 세분화합니다. (여기서, 문서 검색 결과수, `k` 를 의미합니다.)\n",
    "4. 각 지식 스트립을 평가하고 관련성 여부를 score 로 평가합니다. (여기서는 문서 chunk 단위로 평가합니다.)\n",
    "5. 모든 문서가 관련성 임계값 이하이거나 평가 결과 신뢰도가 낮을 경우, 추가 데이터 소스(예: 웹 검색)로 보강합니다.\n",
    "6. 웹 검색을 통한 보강 시, 쿼리 재작성(Query-Rewrite) 을 통해 검색 결과를 최적화합니다.\n",
    "\n",
    "---\n",
    "\n",
    "**주요 내용**\n",
    "\n",
    "이 튜토리얼에서는 LangGraph를 활용하여 CRAG 접근법의 일부 아이디어를 구현합니다.\n",
    "\n",
    "여기서는 **지식 정제 단계는 생략**하고, 필요하다면 노드로 추가할 수 있는 형태로 설계합니다.  \n",
    "\n",
    "또한, **관련 있는 문서가 하나도 없으면** 웹 검색을 활용하여 검색을 보완할 것입니다. \n",
    "\n",
    "웹 검색에는 [Tavily Search](https://python.langchain.com/docs/integrations/tools/tavily_search/)를 사용하고, 검색 최적화를 위해 질문 재작성(Question Re-writing) 을 도입합니다.\n",
    "\n",
    "---\n",
    "\n",
    "**주요 단계 개요**\n",
    "\n",
    "- **Retrieval Grader**: 검색된 문서의 관련성을 평가\n",
    "- **Generate**: LLM을 통한 답변 생성\n",
    "- **Question Re-writer**: 질문 재작성을 통한 검색 질의 최적화\n",
    "- **Web Search Tool**: Tavily Search를 통한 웹 검색 활용\n",
    "- **Create Graph**: LangGraph를 통한 CRAG 전략 그래프 생성\n",
    "- **Use the graph**: 생성된 그래프를 활용하는 방법\n",
    "\n",
    "---\n",
    "\n",
    "**참고**\n",
    "\n",
    "- [LangGraph CRAG 튜토리얼 (공식 문서)](https://langchain-ai.github.io/langgraph/tutorials/rag/langgraph_crag_local/)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a170112a",
   "metadata": {},
   "source": [
    "## 환경 설정\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d3375ae6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# API 키를 환경변수로 관리하기 위한 설정 파일\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# API 키 정보 로드\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6a92d07d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install -qU langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH17-LangGraph-Use-Cases\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e9d52b6",
   "metadata": {},
   "source": [
    "## 기본 PDF 기반 Retrieval Chain 생성\n",
    "\n",
    "여기서는 PDF 문서를 기반으로 Retrieval Chain 을 생성합니다. 가장 단순한 구조의 Retrieval Chain 입니다.\n",
    "\n",
    "단, LangGraph 에서는 Retriever 와 Chain 을 따로 생성합니다. 그래야 각 노드별로 세부 처리를 할 수 있습니다.\n",
    "\n",
    "**참고**\n",
    "\n",
    "- 이전 튜토리얼에서 다룬 내용이므로, 자세한 설명은 생략합니다.\n",
    "\n",
    "**실습에 활용한 문서**\n",
    "\n",
    "소프트웨어정책연구소(SPRi) - 2023년 12월호\n",
    "\n",
    "- 저자: 유재흥(AI정책연구실 책임연구원), 이지수(AI정책연구실 위촉연구원)\n",
    "- 링크: https://spri.kr/posts/view/23669\n",
    "- 파일명: `SPRI_AI_Brief_2023년12월호_F.pdf`\n",
    "\n",
    "_실습을 위해 다운로드 받은 파일을 `data` 폴더로 복사해 주시기 바랍니다_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "4443177d",
   "metadata": {},
   "outputs": [],
   "source": [
    "from rag.pdf import PDFRetrievalChain\n",
    "\n",
    "# PDF 문서를 로드\n",
    "pdf = PDFRetrievalChain([\"data/SPRI_AI_Brief_2023년12월호_F.pdf\"]).create_chain()\n",
    "\n",
    "# retriever 와 chain을 생성\n",
    "pdf_retriever = pdf.retriever\n",
    "pdf_chain = pdf.chain"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ae47f9ae",
   "metadata": {},
   "source": [
    "## 검색된 문서의 관련성 평가 (Question-Retrieval Evaluation)\n",
    "\n",
    "검색된 문서의 관련성 평가는 검색된 문서가 질문과 관련이 있는지 여부를 평가하는 단계입니다.\n",
    "\n",
    "먼저, 검색된 문서를 평가하기 위한 평가기(`retrieval-grader`) 를 생성합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "59f3e68a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_teddynote.models import get_model_name, LLMs\n",
    "from pydantic import BaseModel, Field\n",
    "\n",
    "# 모델 이름 가져오기\n",
    "MODEL_NAME = get_model_name(LLMs.GPT4)\n",
    "\n",
    "\n",
    "# 검색된 문서의 관련성 여부를 이진 점수로 평가하는 데이터 모델\n",
    "class GradeDocuments(BaseModel):\n",
    "    \"\"\"A binary score to determine the relevance of the retrieved document.\"\"\"\n",
    "\n",
    "    # 문서가 질문과 관련이 있는지 여부를 'yes' 또는 'no'로 나타내는 필드\n",
    "    binary_score: str = Field(\n",
    "        description=\"Documents are relevant to the question, 'yes' or 'no'\"\n",
    "    )\n",
    "\n",
    "\n",
    "# LLM 초기화\n",
    "llm = ChatOpenAI(model=MODEL_NAME, temperature=0)\n",
    "\n",
    "# GradeDocuments 데이터 모델을 사용하여 구조화된 출력을 생성하는 LLM\n",
    "structured_llm_grader = llm.with_structured_output(GradeDocuments)\n",
    "\n",
    "# 시스템 프롬프트 정의\n",
    "system = \"\"\"You are a grader assessing relevance of a retrieved document to a user question. \\n \n",
    "    If the document contains keyword(s) or semantic meaning related to the question, grade it as relevant. \\n\n",
    "    Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question.\"\"\"\n",
    "\n",
    "# 채팅 프롬프트 템플릿 생성\n",
    "grade_prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\"system\", system),\n",
    "        (\"human\", \"Retrieved document: \\n\\n {document} \\n\\n User question: {question}\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "# Retrieval 평가기 초기화\n",
    "retrieval_grader = grade_prompt | structured_llm_grader"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d03b7b76",
   "metadata": {},
   "source": [
    "`retrieval_grader` 를 사용해서 문서를 평가합니다. \n",
    "\n",
    "여기서는 문서의 집합이 아닌 1개의 단일 문서에 대한 평가를 수행합니다.\n",
    "\n",
    "결과는 단일 문서에 대한 관련성 여부가 (yes/no) 로 반환됩니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "44144d98",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 질문 정의\n",
    "question = \"삼성전자가 개발한 생성AI 에 대해 설명하세요.\"\n",
    "\n",
    "# 문서 검색\n",
    "docs = pdf_retriever.invoke(question)\n",
    "\n",
    "# 검색된 문서 중 1번 index 문서의 페이지 내용을 추출\n",
    "doc_txt = docs[1].page_content\n",
    "\n",
    "# 검색된 문서와 질문을 사용하여 관련성 평가를 실행하고 결과 출력\n",
    "print(retrieval_grader.invoke({\"question\": question, \"document\": doc_txt}))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c95ae75e",
   "metadata": {},
   "source": [
    "## 답변 생성 체인\n",
    "\n",
    "답변 생성 체인은 검색된 문서를 기반으로 답변을 생성하는 체인입니다.\n",
    "\n",
    "우리가 알고 있는 일반적인 Naive RAG 체인 입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f03e727f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain import hub\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "\n",
    "# LangChain Hub에서 RAG 프롬프트를 가져와 사용\n",
    "prompt = hub.pull(\"teddynote/rag-prompt\")\n",
    "\n",
    "# LLM 초기화\n",
    "llm = ChatOpenAI(model_name=MODEL_NAME, temperature=0)\n",
    "\n",
    "\n",
    "# 문서 포맷팅\n",
    "def format_docs(docs):\n",
    "    return \"\\n\\n\".join(\n",
    "        [\n",
    "            f'<document><content>{doc.page_content}</content><source>{doc.metadata[\"source\"]}</source><page>{doc.metadata[\"page\"]+1}</page></document>'\n",
    "            for doc in docs\n",
    "        ]\n",
    "    )\n",
    "\n",
    "\n",
    "# 체인 생성\n",
    "rag_chain = prompt | llm | StrOutputParser()\n",
    "\n",
    "\n",
    "# 체인 실행 및 결과 출력\n",
    "generation = rag_chain.invoke({\"context\": format_docs(docs), \"question\": question})\n",
    "print(generation)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "963f59f7",
   "metadata": {},
   "source": [
    "## 쿼리 재작성(Question Re-writer)\n",
    "\n",
    "쿼리 재작성은 웹 검색을 최적화하기 위해 질문을 재작성하는 단계입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "81eabae7",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "# LLM 설정\n",
    "llm = ChatOpenAI(model=MODEL_NAME, temperature=0)\n",
    "\n",
    "# Query Rewrite 시스템 프롬프트\n",
    "system = \"\"\"You a question re-writer that converts an input question to a better version that is optimized \n",
    "for web search. Look at the input and try to reason about the underlying semantic intent / meaning.\"\"\"\n",
    "\n",
    "# 프롬프트 정의\n",
    "re_write_prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\"system\", system),\n",
    "        (\n",
    "            \"human\",\n",
    "            \"Here is the initial question: \\n\\n {question} \\n Formulate an improved question.\",\n",
    "        ),\n",
    "    ]\n",
    ")\n",
    "\n",
    "# Question Re-writer 체인 초기화\n",
    "question_rewriter = re_write_prompt | llm | StrOutputParser()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b33f4246",
   "metadata": {},
   "source": [
    "`question_rewriter` 를 사용해서 질문을 재작성합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "28eb2e53",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 실행 및 결과 확인\n",
    "print(f'[원본 질문]: \"{question}\"')\n",
    "print(\"[쿼리 재작성]:\", question_rewriter.invoke({\"question\": question}))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7f4a72cc",
   "metadata": {},
   "source": [
    "## 웹 검색 도구\n",
    "\n",
    "**웹 검색 도구**는 컨텍스트를 보강하기 위한 용도로 사용됩니다.\n",
    "\n",
    "- **웹 검색의 필요성**: 모든 문서가 관련성 임계값을 충족하지 않거나 평가자가 확신이 없을 때, 웹 검색을 통해 추가 데이터를 확보합니다.\n",
    "- **Tavily Search 사용**: Tavily Search를 활용하여 웹 검색을 수행합니다. 이는 검색 쿼리를 최적화하고, 보다 관련성 높은 결과를 제공합니다.\n",
    "- **질문 재작성**: 웹 검색을 최적화하기 위해 질문을 재작성하여 검색 쿼리를 개선합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "620d859b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 웹 검색 도구 초기화\n",
    "from langchain_teddynote.tools.tavily import TavilySearch\n",
    "\n",
    "# 최대 검색 결과를 3으로 설정\n",
    "web_search_tool = TavilySearch(max_results=3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "694d7bae",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 웹 검색 도구 실행\n",
    "results = web_search_tool.invoke({\"query\": question})\n",
    "print(results)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f30cb22",
   "metadata": {},
   "source": [
    "## 상태(State)\n",
    "\n",
    "먼저, CRAG 그래프를 위한 상태를 정의합니다.\n",
    "\n",
    "`web_search` 는 웹 검색을 활용할지 여부를 나타내는 상태입니다. yes 또는 no 로 표현합니다 (yes: 웹 검색 필요, no: 필요 없음)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "b07cb207",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated, List\n",
    "from typing_extensions import TypedDict\n",
    "\n",
    "\n",
    "# 상태 정의\n",
    "class GraphState(TypedDict):\n",
    "    question: Annotated[str, \"The question to answer\"]\n",
    "    generation: Annotated[str, \"The generation from the LLM\"]\n",
    "    web_search: Annotated[str, \"Whether to add search\"]\n",
    "    documents: Annotated[List[str], \"The documents retrieved\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "423bdd4b",
   "metadata": {},
   "source": [
    "## 노드\n",
    "\n",
    "CRAG 그래프에 활용할 노드를 정의합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "4b89e9cb",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.schema import Document\n",
    "\n",
    "\n",
    "# 문서 검색 노드\n",
    "def retrieve(state: GraphState):\n",
    "    print(\"\\n==== RETRIEVE ====\\n\")\n",
    "    question = state[\"question\"]\n",
    "\n",
    "    # 문서 검색 수행\n",
    "    documents = pdf_retriever.invoke(question)\n",
    "    return {\"documents\": documents}\n",
    "\n",
    "\n",
    "# 답변 생성 노드\n",
    "def generate(state: GraphState):\n",
    "    print(\"\\n==== GENERATE ====\\n\")\n",
    "    question = state[\"question\"]\n",
    "    documents = state[\"documents\"]\n",
    "\n",
    "    # RAG를 사용한 답변 생성\n",
    "    generation = rag_chain.invoke({\"context\": documents, \"question\": question})\n",
    "    return {\"generation\": generation}\n",
    "\n",
    "\n",
    "# 문서 평가 노드\n",
    "def grade_documents(state: GraphState):\n",
    "    print(\"\\n==== [CHECK DOCUMENT RELEVANCE TO QUESTION] ====\\n\")\n",
    "    question = state[\"question\"]\n",
    "    documents = state[\"documents\"]\n",
    "\n",
    "    # 필터링된 문서\n",
    "    filtered_docs = []\n",
    "    relevant_doc_count = 0\n",
    "\n",
    "    for d in documents:\n",
    "        # Question-Document 의 관련성 평가\n",
    "        score = retrieval_grader.invoke(\n",
    "            {\"question\": question, \"document\": d.page_content}\n",
    "        )\n",
    "        grade = score.binary_score\n",
    "\n",
    "        if grade == \"yes\":\n",
    "            print(\"==== [GRADE: DOCUMENT RELEVANT] ====\")\n",
    "            # 관련 있는 문서를 filtered_docs 에 추가\n",
    "            filtered_docs.append(d)\n",
    "            relevant_doc_count += 1\n",
    "        else:\n",
    "            print(\"==== [GRADE: DOCUMENT NOT RELEVANT] ====\")\n",
    "            continue\n",
    "\n",
    "    # 관련 문서가 없으면 웹 검색 수행\n",
    "    web_search = \"Yes\" if relevant_doc_count == 0 else \"No\"\n",
    "    return {\"documents\": filtered_docs, \"web_search\": web_search}\n",
    "\n",
    "\n",
    "# 쿼리 재작성 노드\n",
    "def query_rewrite(state: GraphState):\n",
    "    print(\"\\n==== [REWRITE QUERY] ====\\n\")\n",
    "    question = state[\"question\"]\n",
    "\n",
    "    # 질문 재작성\n",
    "    better_question = question_rewriter.invoke({\"question\": question})\n",
    "    return {\"question\": better_question}\n",
    "\n",
    "\n",
    "# 웹 검색 노드\n",
    "def web_search(state: GraphState):\n",
    "    print(\"\\n==== [WEB SEARCH] ====\\n\")\n",
    "    question = state[\"question\"]\n",
    "    documents = state[\"documents\"]\n",
    "\n",
    "    # 웹 검색 수행\n",
    "    docs = web_search_tool.invoke({\"query\": question})\n",
    "    # 검색 결과를 문서 형식으로 변환\n",
    "    web_results = \"\\n\".join([d[\"content\"] for d in docs])\n",
    "    web_results = Document(page_content=web_results)\n",
    "    documents.append(web_results)\n",
    "\n",
    "    return {\"documents\": documents}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "52091fa6",
   "metadata": {},
   "source": [
    "## 조건부 엣지에 활용할 함수\n",
    "\n",
    "`decide_to_generate` 함수는 관련성 평가를 마친 뒤, 웹 검색 여부에 따라 다음 노드로 라우팅하는 역할을 수행합니다.\n",
    "\n",
    "`web_search` 가 `Yes` 인 경우 `query_rewrite` 노드에서 쿼리를 재작성 한 뒤 웹 검색을 수행합니다.\n",
    "\n",
    "만약, `web_search` 가 `No` 인 경우는 `generate` 를 수행하여 최종 답변을 생성합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "49dfc922",
   "metadata": {},
   "outputs": [],
   "source": [
    "def decide_to_generate(state: GraphState):\n",
    "    # 평가된 문서를 기반으로 다음 단계 결정\n",
    "    print(\"==== [ASSESS GRADED DOCUMENTS] ====\")\n",
    "    # 웹 검색 필요 여부\n",
    "    web_search = state[\"web_search\"]\n",
    "\n",
    "    if web_search == \"Yes\":\n",
    "        # 웹 검색으로 정보 보강이 필요한 경우\n",
    "        print(\n",
    "            \"==== [DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, QUERY REWRITE] ====\"\n",
    "        )\n",
    "        # 쿼리 재작성 노드로 라우팅\n",
    "        return \"query_rewrite\"\n",
    "    else:\n",
    "        # 관련 문서가 존재하므로 답변 생성 단계(generate) 로 진행\n",
    "        print(\"==== [DECISION: GENERATE] ====\")\n",
    "        return \"generate\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aaadaa1e",
   "metadata": {},
   "source": [
    "## 그래프 생성\n",
    "\n",
    "이제 노드를 정의하고 엣지를 연결하여 그래프를 완성합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "99977214",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.graph import END, StateGraph, START\n",
    "\n",
    "# 그래프 상태 초기화\n",
    "workflow = StateGraph(GraphState)\n",
    "\n",
    "# 노드 정의\n",
    "workflow.add_node(\"retrieve\", retrieve)\n",
    "workflow.add_node(\"grade_documents\", grade_documents)\n",
    "workflow.add_node(\"generate\", generate)\n",
    "workflow.add_node(\"query_rewrite\", query_rewrite)\n",
    "workflow.add_node(\"web_search_node\", web_search)\n",
    "\n",
    "# 엣지 연결\n",
    "workflow.add_edge(START, \"retrieve\")\n",
    "workflow.add_edge(\"retrieve\", \"grade_documents\")\n",
    "\n",
    "# 문서 평가 노드에서 조건부 엣지 추가\n",
    "workflow.add_conditional_edges(\n",
    "    \"grade_documents\",\n",
    "    decide_to_generate,\n",
    "    {\n",
    "        \"query_rewrite\": \"query_rewrite\",\n",
    "        \"generate\": \"generate\",\n",
    "    },\n",
    ")\n",
    "\n",
    "# 엣지 연결\n",
    "workflow.add_edge(\"query_rewrite\", \"web_search_node\")\n",
    "workflow.add_edge(\"web_search_node\", \"generate\")\n",
    "workflow.add_edge(\"generate\", END)\n",
    "\n",
    "# 그래프 컴파일\n",
    "app = workflow.compile()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c98c318",
   "metadata": {},
   "source": [
    "그래프를 시각화합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "71047d74",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_teddynote.graphs import visualize_graph\n",
    "\n",
    "visualize_graph(app)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4357c3d7",
   "metadata": {},
   "source": [
    "## 그래프 실행\n",
    "\n",
    "이제 그래프를 실행하고 결과를 확인합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b8a06ddb",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.runnables import RunnableConfig\n",
    "from langchain_teddynote.messages import stream_graph, invoke_graph, random_uuid\n",
    "\n",
    "# config 설정(재귀 최대 횟수, thread_id)\n",
    "config = RunnableConfig(recursion_limit=20, configurable={\"thread_id\": random_uuid()})\n",
    "\n",
    "# 질문 입력\n",
    "inputs = {\n",
    "    \"question\": \"삼성전자가 개발한 생성형 AI 의 이름은?\",\n",
    "}\n",
    "\n",
    "# 스트리밍 형식으로 그래프 실행\n",
    "stream_graph(\n",
    "    app,\n",
    "    inputs,\n",
    "    config,\n",
    "    [\"retrieve\", \"grade_documents\", \"query_rewrite\", \"web_search_node\", \"generate\"],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "28667d37",
   "metadata": {},
   "outputs": [],
   "source": [
    "# config 설정(재귀 최대 횟수, thread_id)\n",
    "config = RunnableConfig(recursion_limit=20, configurable={\"thread_id\": random_uuid()})\n",
    "\n",
    "# 질문 입력\n",
    "inputs = {\n",
    "    \"question\": \"2024년 노벨문학상 수상자의 이름은?\",\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bbccba94",
   "metadata": {},
   "source": [
    "그래프를 실행합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e865dcc1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 그래프 실행\n",
    "invoke_graph(app, inputs, config)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0e5f3b5",
   "metadata": {},
   "source": [
    "스트리밍 형식으로 그래프를 실행합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d864b22d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 그래프 실행\n",
    "stream_graph(\n",
    "    app,\n",
    "    inputs,\n",
    "    config,\n",
    "    [\"retrieve\", \"grade_documents\", \"query_rewrite\", \"generate\"],\n",
    ")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-kr-lwwSZlnu-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
